Skip to content

fix: prevent manifest timestamp churn on unchanged updates - #2846

Closed
hopstreax wants to merge 1 commit into
Graphify-Labs:v8from
hopstreax:fix/2838-manifest-seen-timestamps
Closed

fix: prevent manifest timestamp churn on unchanged updates#2846
hopstreax wants to merge 1 commit into
Graphify-Labs:v8from
hopstreax:fix/2838-manifest-seen-timestamps

Conversation

@hopstreax

Copy link
Copy Markdown
Contributor

Summary

Fixes #2838.

graphify update was rewriting graphify-out/manifest.json on every run by updating the per-file seen timestamp, even when the source files and graph topology were unchanged.

This caused graphify-out/ to remain dirty indefinitely and made the pre-push hook create a trailing graph commit on every push.

What changed

Validation

Verified that:

  • unchanged files preserve their existing seen timestamp
  • changed files receive a new seen timestamp and updated hash
  • same-mtime/same-tick rewrites are still detected by incremental detection
  • AST → semantic tier transitions continue to work
  • manifest pruning and clear directives remain intact
  • malformed and legacy manifests are handled safely
  • consecutive no-op graphify update . runs leave manifest.json byte-identical
  • graphify-out/ remains clean after consecutive no-op updates
  • the pre-push graph commit workflow now converges

Relevant tests pass, including the manifest, incremental mtime collision, partial cache, and atomic write suites.

The remaining Windows-specific failures in the broader suite were reproduced on the parent commit and are unrelated to this change.

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 5 advisory finding(s) below merit a look before merge.

Formal verification. 4 change(s) tested, no difference found (not proven).


Graphify review — findings

Reverts the unreleased 0.9.46 changes: drops Common Lisp extraction support (README tables, [commonlisp] extra) and removes several build-time fixes. Removes the specific-over-generic relation collapse guard (_GENERIC_RELATIONS) in build_from_json, drops hyperedge member rewiring on dedup in build, and removes the stranded external-import stub sweep after source pruning in build_merge. Folds the reverted CHANGELOG entries back and remarks 0.9.45 as unreleased.

Worth a look

  • Generic edge relations can overwrite specific relations againgraphify/build.py:1230 · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review Execution auto-disposal is off for this run; enable it (with sandbox isolation) to have Graphify try to confirm or refute this automatically.
  • Reverting generic-relation collapse allows specific edges to be downgraded to genericgraphify/build.py:1233 · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review Execution auto-disposal is off for this run; enable it (with sandbox isolation) to have Graphify try to confirm or refute this automatically.
  • Removed public hyperedges keyword from deduplicate_entitiesgraphify/dedup.py:467 · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review Execution auto-disposal is off for this run; enable it (with sandbox isolation) to have Graphify try to confirm or refute this automatically.
  • Hyperedge members no longer rewired to dedup survivors, losing participantsgraphify/dedup.py:782 · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review Execution auto-disposal is off for this run; enable it (with sandbox isolation) to have Graphify try to confirm or refute this automatically.
  • Common Lisp extensions no longer classified as codegraphify/detect.py:46 · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review Execution auto-disposal is off for this run; enable it (with sandbox isolation) to have Graphify try to confirm or refute this automatically.
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 4564 functions depend on the 2317 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract() — 464 callers, 41 callees
  • new: _rebuild_code() — 98 callers, 51 callees
  • new: build_from_json() — 153 callers, 18 callees
  • new: detect() — 99 callers, 15 callees
  • new: deduplicate_entities() — 63 callers, 20 callees
  • new: build_merge() — 43 callers, 14 callees
  • new: save_semantic_cache() — 51 callers, 9 callees
  • new: _extract_generic() — 18 callers, 24 callees
  • …and 141 more — each is listed as a finding

Verification — 4564 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 4226 function(s) in the blast radius were not formally verified this run

Formal verification

Could not verify: Could not verify build.

The verifier did not have enough to check build, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: not verifiable: all 9 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly AttributeError — names the real obstacle, not a sampling gap)

Could not verify: Could not verify build\_from\_json.

The verifier did not have enough to check build\_from\_json, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: not verifiable: all 6 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly NameError — names the real obstacle, not a sampling gap)

Could not verify: Could not verify build\_merge.

The verifier did not have enough to check build\_merge, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `graph_path` is annotated `str | Path | None` — outside the synthesizable primitive/collection set

Could not verify: Could not verify deduplicate\_entities.

The verifier did not have enough to check deduplicate\_entities, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: not verifiable: all 9 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly TypeError — names the real obstacle, not a sampling gap)

Could not verify: Could not verify \_load\_dir\_own\_ignore.

The verifier did not have enough to check \_load\_dir\_own\_ignore, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `d` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify \_load\_graphifyignore.

The verifier did not have enough to check \_load\_graphifyignore, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `root` is annotated `Path` — outside the synthesizable primitive/collection set

No difference found (not proven): No behavior difference found in save\_manifest (not a proof).

The verifier ran both versions of save\_manifest on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.

Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.

Note: An input the sampler did not try could still differ.

Could not verify: Could not verify \_extract\_generic.

The verifier did not have enough to check \_extract\_generic, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify \_java\_annotation\_names.

The verifier did not have enough to check \_java\_annotation\_names, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: not verifiable: all 182 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly AttributeError — names the real obstacle, not a sampling gap)

Could not verify: Could not verify \_java\_collect\_type\_refs.

The verifier did not have enough to check \_java\_collect\_type\_refs, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `skip` is annotated `frozenset[str] | None` — outside the synthesizable primitive/collection set

Could not verify: Could not verify extract\_markdown.

The verifier did not have enough to check extract\_markdown, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify \_resolve\_go\_type\_references.

The verifier did not have enough to check \_resolve\_go\_type\_references, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: parameter `root` is annotated `Path` — outside the synthesizable primitive/collection set

Could not verify: Could not verify \_resolve\_java\_type\_references.

The verifier did not have enough to check \_resolve\_java\_type\_references, so it is saying so rather than guessing. No false assurance is the whole point.

Guarantee: No guarantee either way, this is an honest abstention, not a pass.

Note: Reason: non-vacuity: domain too small (only 1 distinct inputs exercised, need 3) — 'no divergence' would be near-vacuous

No difference found (not proven): No behavior difference found in normalize\_id (not a proof).

The verifier ran both versions of normalize\_id on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.

Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.

Note: An input the sampler did not try could still differ.

No difference found (not proven): No behavior difference found in \_search\_tokens (not a proof).

The verifier ran both versions of \_search\_tokens on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.

Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.

Note: An input the sampler did not try could still differ.

No difference found (not proven): No behavior difference found in \_subgraph\_to\_text (not a proof).

The verifier ran both versions of \_subgraph\_to\_text on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.

Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.

Note: An input the sampler did not try could still differ.

· 149 more finding(s) on lines outside this diff (see the check run).

@safishamsi

Copy link
Copy Markdown
Collaborator

Shipped in v0.9.47 via authorship-preserving cherry-pick, with added no-op-then-change and corrupt-manifest tests. Thanks @hopstreax! Release: https://github.com/Graphify-Labs/graphify/releases/tag/v0.9.47

@safishamsi safishamsi closed this Aug 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

manifest.json rewrites per-file "seen" timestamps on every run, so graphify-out/ is never clean and the pre-push hook never converges

2 participants